module Functions where

{- All the required imports -}
import Data.ByteString      (ByteString, pack, unpack)
import Data.Char            (ord, chr, isDigit)
import Data.List            (isPrefixOf, isInfixOf, isSuffixOf)
import Data.Maybe           (mapMaybe)
import Data.Word            (Word8)
import Network.CGI.Protocol (replace)
import Network.HTTP         (getResponseBody, getRequest, simpleHTTP)
import System.IO.Unsafe     (unsafePerformIO)
import System.Clock         (getTime, Clock (Realtime), sec)
import Types


{- GENERAL FUNCTIONS -}

-- Get unix time and returns it as IO Int
getTimeSec :: IO Int
getTimeSec = do timespec <- getTime Realtime
                return $ sec timespec
                
-- Removes spaces on both sides of a string
clean :: String -> String
clean s = reverse $ cleanSpace $ reverse $ cleanSpace s
          where cleanSpace = dropWhile (' '==)
          
-- Splits a list with a certain element.
-- Ex: splitWith ',' "list,of,words" -> ["list", "of", "words"]
splitWith :: Eq a => a -> [a] -> [[a]]
splitWith _ [] = []
splitWith l ls = takeWhile (/= l) ls : splitWith l (drop 1 $ dropWhile (/= l) ls)

-- Used for [itemid,itemdrop,itemid,itemdrop,...] -> [(itemid,itemdrop), (itemid,itemdrop), ...]
listToPair :: [a] -> [(a,a)]
listToPair [] = []
listToPair (l:[]) = []
listToPair (l:s:ls) = (l,s) : listToPair ls

-- Easier way to open a website and return it as IO String
openURL x = simpleHTTP (getRequest x) >>= getResponseBody

-- Given unix time in Int, it turns it in to a string
timeToString :: Int -> String
timeToString n 
  | n >= 86400 = show (n `div` 86400) ++ " days "    ++ timeToString (n `mod` 86400)
  | n >= 3600  = show (n `div` 3600)  ++ " hours "   ++ timeToString (n `mod` 3600)
  | n >= 60    = show (n `div` 60)    ++ " minutes " ++ timeToString (n `mod` 60)
  | otherwise  = show n               ++ " seconds "

  
{- CONVERTING FUNCTIONS -}

wordToByte :: [Word8] -> ByteString
wordToByte mes = pack $ mes ++ [10]

byteToWord :: ByteString -> [Word8]
byteToWord = unpack

stringToWord :: String -> [Word8]
stringToWord = map (fromIntegral . ord)

wordToString :: [Word8] -> String
wordToString = map (chr . fromIntegral)

{- BOT FUNCTIONS -}

-- Changes the Bot to Ready
ready :: Bot -> Bot
ready (Bot so st ni lo co sy pa up di bu it mo) = Bot so Ready ni lo co sy pa up di bu it mo

-- Changes the Bot to Locked
locked :: Bot -> Bot
locked (Bot so st ni lo co sy pa up di bu it mo) = Bot so Locked ni lo co sy pa up di bu it mo

-- Updates last recieved message in bot
botMes :: Bot -> [Word8] -> Bot
botMes (Bot so st ni lo co sy pa up di bu it mo) bu' = Bot so st ni lo co sy pa up di bu' it mo

-- Checks if a user exists in Disconnected list, adds/updates the user
botAddDis :: Bot -> String -> Int -> Bot
botAddDis (Bot so st ni lo co sy pa up di bu it mo) di' ti = Bot so st ni lo co sy pa up (recAddDis di di' ti) bu it mo
recAddDis :: [(String,Int)] -> String -> Int -> [(String,Int)]
recAddDis [] s t     = [(s,t)]
recAddDis (d:ds) s t | fst d == s = (s,t) : ds
                     | otherwise  = d : recAddDis ds s t

-- Checkis if a user exists in Disconnected list, removes if found
botRemDis :: Bot -> String -> Bot
botRemDis (Bot so st ni lo co sy pa up di bu it mo) di' = Bot so st ni lo co sy pa up (recRemDis di di') bu it mo
recRemDis :: [(String,Int)] -> String -> [(String,Int)]
recRemDis [] s = []
recRemDis (d:ds) s | fst d == s = ds
                   | otherwise  = d : recRemDis ds s


                   
{- PATTERN FUNCTIONS -}

-- Send private message, adds black background and white text as default
send :: String -> String
send message = "PRIVMSG " ++ channel message ++ " :" ++ wordToString [03,48,44,49,32]

-- Returns channel from message
channel :: String -> String
channel = head . filter (\x->head x == '#') . words

-- Returns what a PING sent
pong :: Bot -> String
pong bot = "PONG" ++ (drop 4 . wordToString . buffer) bot

-- Reads SVN info from the link below and sorts out information, not always correct on reas (info about commit)
svn :: String -> String
svn message = do let src = unsafePerformIO $ openURL "http://rathena.svn.sourceforge.net/viewvc/rathena/"   -- Not in a monad so unsafe is needed, not recommended but works
                 let lin = drop 9 $ dropWhile (\x->take 15 x /= "<a name=\"trunk\"") $ lines src            -- This code section
                 let vers = takeWhile isDigit $ dropWhile (not . isDigit) $ head lin                        -- is a bit messy but
                 let time = reverse $ drop 5 $ reverse $ drop 10 $ lin !! 2                                 -- does the job, can
                 let user = reverse $ drop 5 $ reverse $ drop 10 $ lin !! 3                                 -- be optimised.....
                 let reas = reverse $ drop 5 $ reverse $ drop 10 $ concat $ takeWhile ([]/=) $ drop 6 lin   -- ........... a lot
                 "Current Rev:" ++ wordToString [03,52,32] ++ vers ++ wordToString [03,48] ++ "  by" ++ wordToString [03,52,32] ++ user ++ wordToString [03,48] ++ " " ++ time ++ " ago. \n" ++ send message ++ reas

-- Returns code for white text
white :: String
white = wordToString [03,48]

-- Returns code for red text
red :: String
red = wordToString [03,52]

-- Returns code for blue text
blue :: String
blue = wordToString [03,49,49]

-- Checks if the user used !lastonline or !lo, used for sorting the name
lastOnline :: Bot -> String -> Int -> String
lastOnline b m t = lastOnline' b m t (if "!lastonline" `isInfixOf` m then "!lastonline" else "!lo")

-- Checks if the name exists in the Disconnected users
lastOnline' :: Bot -> String -> Int -> String -> String
lastOnline' bot mes time sea = do let name = concat $ drop 1 $ dropWhile (not . isInfixOf sea) $ words mes            -- Gets the name searched for
                                  let foundList = mapMaybe (\(x,y)->if name `isInfixOf` x then                        -- Maps the name over the list, returns
                                                                      Just (x,y)                           -- List with the names found
                                                                      else Nothing) (disconnected bot)          -- Empty list if none is found
                                  case length foundList of  -- Length is how many results are found, the text below should explain the options
                                    0 -> send mes ++ "No record of user [" ++ name ++ "]"
                                    1 -> send mes ++ "User [" ++ (fst $ head foundList) ++ "] disconnected " ++ timeToString (time-(snd $ head foundList)) ++ " ago"
                                    o -> send mes ++ (show o) ++ " records found with [" ++ name ++ "]. Please be more specific."
                                                
-- Finds items searched for, * is used for wildcard
il :: Bot -> String -> String
il bot mes = do let list = map clean $ splitWith ',' $ unwords $ drop 1 $ dropWhile (not . isInfixOf "!il") $ words mes
                let itemlist = (items bot)
                let found = map (\name->mapMaybe (\x->if name == (iName x) || name == (replace '_' ' ' $ iAegis x) || name == (iAegis x) || name == (iID x) then
                                                        Just (iID x)
                                                        else Nothing) itemlist) list
                send mes ++ (drop 1 $ init $ init $ show(concatMap (\x-> x ++ ",") $ concatMap (\x->case x of
                                                                                                   [] -> ["0"]
                                                                                                   ot -> x) found))
                
sil :: Bot -> String -> String
sil bot mes = do let list = map clean $ splitWith ',' $ unwords $ drop 1 $ dropWhile (not . isInfixOf "!sil") $ words mes
                 let itemlist = (items bot)
                 let found = map (\name->mapMaybe (\x->if name == (iName x) || name == (replace '_' ' ' $ iAegis x) || name == (iAegis x) || name == (iID x) then
                                                         Just ((iID x) ++ " " ++ (iScript x) ++ ", ")
                                                         else Nothing) itemlist) list
                 send mes ++ (init $ init $ concat $ concatMap (\x->case x of
                                                                     [] -> ["0, "]
                                                                     ot -> x) found)

item :: Bot -> String -> String
item bot mes = do let name' = unwords $ drop 1 $ dropWhile (not . isInfixOf "!item") $ words mes    -- Gets the name searched for
                  let (name,function) = case name' of   -- Checks if any wildcards are used, then return the name and function to use in the searching
                                          ('*':rest) -> case reverse rest of
                                                          ('*':rest') -> (reverse rest',isInfixOf)          -- Wildcards at both sides
                                                          otherwise   -> (reverse otherwise,isSuffixOf)     -- Wildcard at beginning
                                          otherwise  -> case reverse otherwise of
                                                          ('*':rest') -> (reverse rest',isPrefixOf)         -- Wildcard at end
                                                          otherwise'   -> (reverse otherwise',isEqualTo)    -- No wildcards
                  let foundList = mapMaybe (\x->if function name (iName x) || name == (iID x) then          -- Checks for name or ID match
                                                  Just ((iName x) ++ " [" ++ (iID x) ++ "]")       -- Same system as the function above
                                                  else Nothing) (items bot)
                  case length foundList of
                    0 -> send mes ++ "No item found by: [" ++ name ++ "]"
                    1 -> send mes ++ head foundList
                    o -> send mes ++ " " ++ (show o) ++ " items found with [" ++ name ++ "]. Showing the first:\n" ++ send mes ++ head foundList

-- Finds mobs searched for, * is used for wildcard, works the same as item above
ml :: Bot -> String -> String
ml bot mes = do let list = map clean $ splitWith ',' $ unwords $ drop 1 $ dropWhile (not . isInfixOf "!ml") $ words mes
                let itemlist = (mobs bot)
                let found = map (\name->mapMaybe (\x->if name == (mName x) || name == (replace '_' ' ' $ mAegis x) || name == (mAegis x) || name == (mID x) then
                                                        Just (mID x)
                                                        else Nothing) itemlist) list
                send mes ++ (drop 1 $ init $ init $ show(concatMap (\x->x++",") $ concat $ map (\x->case x of
                                                                                                     [] -> ["0"]
                                                                                                     ot -> x) found))

mob :: Bot -> String -> String
mob bot mes = do let name' = unwords $ drop 1 $ dropWhile (not . isInfixOf "!mob") $ words mes
                 let (name,function) = case name' of
                                         ('*':rest) -> case reverse rest of
                                                         ('*':rest') -> (reverse rest',isInfixOf)
                                                         otherwise   -> (reverse otherwise,isSuffixOf)
                                         otherwise  -> case reverse otherwise of
                                                         ('*':rest') -> (reverse rest',isPrefixOf)
                                                         otherwise'   -> (reverse otherwise',isEqualTo)
                 let foundList = mapMaybe (\x->if function name (mName x) || name == (mID x) then
                                                 do let (cID,cDrop) = mCard x
                                                    Just ((mName x) ++ "(" ++ (mAegis x) ++ ") [" ++ (mID x) ++ "] | LV: " ++ (mLV x) ++ " | HP: " ++ (mHP x) ++ " | EXP: " ++ (mEXP x) ++ " | JEXP: " ++ (mJEXP x)
                                                                   ++ " | Card: " ++ cID)
                                                 else Nothing) (mobs bot)
                 case length foundList of
                   0 -> send mes ++ "No mob found by: [" ++ name ++ "]"
                   1 -> send mes ++ head foundList
                   o -> send mes ++ " " ++ (show o) ++ " mobs found with [" ++ name ++ "]. Showing the first:\n" ++ send mes ++ head foundList

-- Didn't exist so I added it for the two functions above
isEqualTo :: Eq a => [a] -> [a] -> Bool
isEqualTo a b = a == b

leave :: String
leave = undefined






